namespace FsharpCad

#if acad
open Autodesk.AutoCAD.ApplicationServices.Core
open Autodesk.AutoCAD.GraphicsInterface
open Autodesk.AutoCAD.DatabaseServices
open Autodesk.AutoCAD.EditorInput
open Autodesk.AutoCAD.Geometry
#endif

#if zcad
open ZwSoft.ZwCAD.ApplicationServices.Core
open ZwSoft.ZwCAD.GraphicsInterface
open ZwSoft.ZwCAD.DatabaseServices
open ZwSoft.ZwCAD.EditorInput
open ZwSoft.ZwCAD.Geometry
#endif

open System
open System.Collections.Generic
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Core
open Library
type GeoEnt =
    | GeoLine of startPoint: Point3d * endPoint: Point3d
    | GeoCircle of center: Point3d * radius: double
    | GeoArc of startPoint: Point3d * pt: Point3d * endPoint: Point3d
    | GeoPolyline of Point3d array
    | GeoEllipticalArc of
        center: Point3d *
        majorAxisLength: double *
        minorAxisLength: double *
        startDegreeInRads: double *
        endDegreeInRads: double *
        tiltDegreeInRads: double
    | GeoText of
        position: Point3d *
        direction: Vector3d *
        height: double *
        width: double *
        oblique: double *
        message: string
    | GeoXline of pt1: Point3d * pt2: Point3d
    | GeoRay of pt1: Point3d * pt2: Point3d

type Keep =
    | KeepInDatabase
    | NotInDatabase

type DrawJigEx(pt: Point3d, ents: Entity array) =
    inherit DrawJig()

    let ucs =
        Application.DocumentManager.MdiActiveDocument.Editor.CurrentUserCoordinateSystem

    let geoents = List<GeoEnt * Keep>()

    let updateAction = List<Point3d -> unit>()
    let mutable currentPoint = pt

    let mutable lastPoint = pt
    let entities = List<Entity>(ents)

    let option = JigPromptPointOptions()

    let keyAction = Dictionary<string, DrawJigEx -> unit>()

    do option.AppendKeywordsToMessage <- true

    let drawGeoEntity (geo: Geometry) =
        for geoEnt, _ in geoents do
            match geoEnt with
            | GeoLine(startpt, endpt) -> geo.WorldLine(startpt, endpt) |> ignore
            | GeoCircle(center, radius) -> geo.Circle(center, radius, Vector3d.ZAxis) |> ignore
            | GeoArc(startpt, pt, endpt) -> geo.CircularArc(startpt, pt, endpt, ArcType.ArcSimple) |> ignore
            | GeoPolyline point3ds ->
                geo.Polyline(new Point3dCollection(point3ds), Vector3d.ZAxis, IntPtr.Zero)
                |> ignore
            | GeoEllipticalArc(center,
                               majorAxisLength,
                               minorAxisLength,
                               startDegreeInRads,
                               endDegreeInRads,
                               tiltDegreeInRads) ->
                geo.EllipticalArc(
                    center,
                    Vector3d.ZAxis,
                    majorAxisLength,
                    minorAxisLength,
                    startDegreeInRads,
                    endDegreeInRads,
                    tiltDegreeInRads,
                    ArcType.ArcSimple
                )
                |> ignore
            | GeoText(position, direction, height, width, oblique, message) ->
                geo.Text(position, Vector3d.ZAxis, direction, height, width, oblique, message)
                |> ignore
            | GeoXline(pt1, pt2) -> geo.Xline(pt1, pt2) |> ignore
            | GeoRay(pt1, pt2) -> geo.Ray(pt1, pt2) |> ignore

    let updateEntity (geo: Geometry) =
        for func in updateAction do
            func currentPoint

        for entity in entities do
            geo.Draw(entity) |> ignore

    let AcquireInput (prompts: JigPrompts, options: JigPromptPointOptions) =
        option.UserInputControls <-
            UserInputControls.Accept3dCoordinates
            ||| UserInputControls.NullResponseAccepted
            ||| UserInputControls.NoNegativeResponseAccepted

        let result = prompts.AcquirePoint(options)

        if result.Status <> PromptStatus.OK && result.Status <> PromptStatus.Keyword then
            SamplerStatus.Cancel
        elif result.Value.IsEqualTo(currentPoint) then
            SamplerStatus.NoChange
        else
            currentPoint <- result.Value
            SamplerStatus.OK

    member this.addKey key action =
        match key with
        | RegMatch @"[A-Z]+" g ->
            let f = g[0].Value
            let contains = keyAction.ContainsKey f

            if not contains then
                option.Keywords.Add(f, f, key)
                keyAction.Add(f, action)
        | _ -> ()

    member this.addMessage str = option.Message <- $"\n{str}"

    member this.addOption action = action option

    member this.invokeKey key =
        let has, action = keyAction.TryGetValue key

        if has then
            action this

    member this.addAction action = updateAction.Add action

    member this.DrawGeo geoent keep = geoents.Add(geoent, keep)

    member this.updatePoint() =
        if not (lastPoint.IsEqualTo(currentPoint)) then
            lastPoint <- currentPoint

    member this.Entities = entities
    member this.GeoEntities = geoents

    member this.CurrentPoint = currentPoint

    member this.LastPoint = lastPoint

    override this.WorldDraw(draw) =
        let geo = draw.Geometry

        if not (isNull geo) then
            geo.PushModelTransform(ucs) |> ignore

            if not draw.RegenAbort then
                drawGeoEntity geo // 绘制临时图形
                geoents.Clear() // 绘制后清除当前缓冲区的临时图元

            if not draw.RegenAbort then
                updateEntity geo // 更新外部图形,并重新绘制图形

            geo.PopModelTransform() |> ignore

        true

    override this.Sampler(prompts) = AcquireInput(prompts, option)

module Jig =
    let addkey key (action: DrawJigEx -> unit) (jig: DrawJigEx) =
        jig.addKey key action
        jig

    let addmessage str (jig: DrawJigEx) =
        jig.addMessage str
        jig

    let addoption action (jig: DrawJigEx) =
        jig.addOption action
        jig

    let addaction action (jig: DrawJigEx) =
        jig.addAction action
        jig

    let inline private addto (jig: DrawJigEx) =
        jig.GeoEntities
        |> Seq.iter (fun (ent, keep) ->
            if keep = KeepInDatabase then
                let inent: Entity =
                    match ent with
                    | GeoLine(startpt, endpt) -> new Line(startpt, endpt)
                    | GeoCircle(center, radius) -> new Circle(center, Vector3d.ZAxis, radius)
                    | GeoArc(startpt, pt, endpt) ->
                        let arc = new CircularArc3d(startpt, pt, endpt)
                        let arcc = Curve.CreateFromGeCurve(arc) :?> Arc
                        arcc
                    | GeoPolyline point3ds ->
                        let pl = new Polyline()
                        pl.SetDatabaseDefaults()

                        point3ds
                        |> Array.iteri (fun i pt -> pl.AddVertexAt(i, Point2d(pt.X, pt.Y), 0, 0, 0))

                        pl
                    | GeoEllipticalArc(center,
                                       majorAxisLength,
                                       minorAxisLength,
                                       startDegreeInRads,
                                       endDegreeInRads,
                                       tiltDegreeInRads) ->
                        let earc =
                            new Ellipse(
                                center,
                                Vector3d.ZAxis,
                                rad_to_vector3d tiltDegreeInRads,
                                majorAxisLength / minorAxisLength,
                                startDegreeInRads,
                                endDegreeInRads
                            )

                        earc
                    | GeoText(position, direction, height, width, oblique, message) ->
                        let txt = new DBText()
                        txt.SetDatabaseDefaults()
                        txt.Height <- height
                        txt.TextString <- message
                        txt.Position <- position
                        txt.Oblique <- oblique
                        txt.WidthFactor <- width
                        txt.Rotation <- direction.GetAngleTo(Vector3d.XAxis)
                        txt
                    | GeoXline(pt1, pt2) ->
                        let xl = new Xline()
                        xl.BasePoint <- pt1
                        xl.SecondPoint <- pt2
                        xl
                    | GeoRay(pt1, pt2) ->
                        let ray = new Ray()
                        ray.BasePoint <- pt1
                        ray.SecondPoint <- pt2
                        ray

                jig.Entities.Add(inent))

    let rec run (jig: DrawJigEx) =
        let pr = Application.DocumentManager.MdiActiveDocument.Editor.Drag(jig)

        match pr.Status with
        | PromptStatus.Keyword ->
            jig.invokeKey pr.StringResult
            run jig
        | PromptStatus.OK ->
            jig.updatePoint ()

            if jig.GeoEntities.Count > 0 then
                addto jig

            jig.Entities
        | _ -> run jig

    let rec runloop (action: DrawJigEx -> unit) (jig: DrawJigEx) =
        let pr = Application.DocumentManager.MdiActiveDocument.Editor.Drag(jig)

        match pr.Status with
        | PromptStatus.Keyword ->
            jig.invokeKey pr.StringResult
            runloop action jig
        | PromptStatus.OK ->
            jig.updatePoint ()

            if jig.GeoEntities.Count > 0 then
                addto jig
                jig.GeoEntities.Clear()

            action jig
            runloop action jig
        | _ -> jig.Entities
